#ifndef GAMEENGINE_GEOMETRY_COLUMN_HPP
#define GAMEENGINE_GEOMETRY_COLUMN_HPP

#include <GameEngine/Geometry/Surfaces/plane3d.h>

#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=================================================================================
// Implementation of the column class

//! Default constructor
/*!
 @note Class S should have a default constructor
 @note Class T should have a default constructor
 */
template <class S, class T>
Column<S,T>::Column() {}

//! Constructor from a base surface, an extrusion vector and an interval
/*!
 @param base The base surface of the column
 @param extrusion_vector The orientation along which the base of the column is extruded
 @param interval The interval along which the base of the column is extruded
 @note Class S should have a copy constructor
 @note Class T should have a copy constructor
 */
template <class S, class T>
Column<S,T>::Column(const S& base, const Vector<T>& extrusion_vector, const Interval<T>& interval) :
	base_(base), extrusion_vector_(extrusion_vector), interval_(interval) {}

//! Copy constructor
/*!
 @param rhs The column to copy
 @note Class S should have a copy constructor
 @note Class T should have a copy constructor
 */
template <class S, class T>
Column<S,T>::Column(const Column& rhs) :
	base_(rhs.base_), extrusion_vector_(rhs.extrusion_vector_), interval_(rhs.interval_) {}

//! Destructor
/*!
 @note The destructor of class S should be public
 @note The destructor of class T should be public
 */
template <class S, class T>
Column<S,T>::~Column() {}

//! Assignment operator
/*!
 @param rhs The column to copy
 @note Class S should have an assignment operator
 @note Class T should have an assignment operator
 */
template <class S, class T>
Column<S,T>& Column<S,T>::operator=(const Column& rhs) {
	if ( &rhs != this ) {
		base_ = rhs.base_;
		extrusion_vector_ = rhs.extrusion_vector_;
		interval_ = rhs.interval_;
	}
	return *this;
}

//! Accessor to the base of the column (read-write)
/*!
 @return A reference to the base of the column
 */
template <class S, class T>
S& Column<S,T>::base() {
	return base_;
}

//! Accessor to the base of the column (read only)
/*!
 @return A constant reference to the base of the column
 */
template <class S, class T>
const S& Column<S,T>::base() const {
	return base_;
}

//! Accessor to the extrusion vector of the column (read-write)
/*!
 @return A reference to the extrusion vector of the column
 */
template <class S, class T>
Vector3d<T>& Column<S,T>::extrusion_vector() {
	return extrusion_vector_;
}

//! Accessor to the extrusion vector of the column (read only)
/*!
 @return A constant reference to the extrusion vector of the column
 */
template <class S, class T>
const Vector3d<T>& Column<S,T>::extrusion_vector() const {
	return extrusion_vector_;
}

//! Accessor to the extrusion interval of the column (read-write)
/*!
 @return A reference to the extrusion interval of the column
 */
template <class S, class T>
Interval<T>& Column<S,T>::interval() {
	return interval_;
}

//! Accessor to the extrusion interval of the column (read only)
/*!
 @return A constant reference to the extrusion interval of the column
 */
template <class S, class T>
const Interval<T>& Column<S,T>::interval() const {
	return interval_;
}

//! Compute the area of the column
/*!
 @return The area of the column, i.e., 2*base.area() + base.perimeter()*interval.length()
 @note Return -1 if interval is not finite
 @note Type S should have a S::area() function, returning the area of the base surface,
       in a format that is compatible with type T
 @note Type S should have a S::perimeter() function, returning the perimeter of the base
       surface, in a format that is compatible with type T
 */
template <class S, class T>
T Column<S,T>::area() const {
	// Check for infinite or semi-infinite intervals
	if ( interval_.finite_interval() == false ) return (T)-1;

	// Return area
	return (T)(2.0*base_.area() + interval_.length()*base_.perimeter());
}

//! Compute the volume of the column
/*!
 @return The volume of the column, i.e., base.area()*interval.length()
 @note Return -1 if interval is not finite
 @note Type S should have a S::area() function, returning the area of the base surface,
       in a format that is compatible with type T
 */
template <class S, class T>
T Column<S,T>::volume() const {
	// Check for infinite or semi-infinite intervals
	if ( interval_.finite_interval() == false ) return (T)-1;

	// Return volume
	return (T)(base_.area()*interval_.length());
}

//! Compute the volume of the column
/*!
 @return The volume of the column, i.e., base.area()*interval.length()
 @note Return -1 if interval is not finite
 @note Type S should have a S::area() function, returning the area of the base surface,
       in a format that is compatible with type T
 */
template <class S, class T>
T Column<S,T>::volume() const {
	// Check for infinite or semi-infinite intervals
	if ( interval_.finite_interval() == false ) return (T)-1;

	// Return volume
	return (T)(base_.area()*interval_.length());
}

//! Test wether a point is inside the column
/*!
 *  If the column is degenerate, this function always returns false
 *
 @param rhs The point to test for inclusion
 @param strict If set to true, a point located exactly on the perimeter of
			   the column is not considered to belong to the column
 @return True if the point is inside the circle, false otherwise
 @note Type S should have a S::plane() function, returning the 3D plane in
       which the base surface lies typed as a Plane3d<T>
 @note Type S should have a S::inside(const Point3d<T>& rhs, bool strict)
       function, which returns true if given point is in the base surface
	   (strictly or not) and false otherwise
 */
template <class S, class T>
bool Column<S,T>::inside(const Point3d<T>& rhs, bool strict) const {
	// Projet point in the plane of the base surface
	Plane3d<T> plane = base_.plane();
	Point3d<T> projection;
	if ( plane.project(rhs, extrusion_vector_, projection) == false ) return false;

	// Check if projected point is inside the base surface
	if ( base_.inside(projection, strict) == false ) return false;

	// Get the signed distance between the points
	Vector3d<T> direction = extrusion_vector_;
	direction.normalize();
	Line3d<T> line(rhs, Point3d<T>(rhs.x()+direction.x(), rhs.y()+direction.y(), rhs.z()+direction.z()));
	T signed_distance = line.closest_point(projection);

	// Check whether signed distance belongs to the interval
	Interval<T> interval = interval_;
	interval.min_included() = ( strict == false );
	interval.max_included() = ( strict == false );
	return interval.inside(signed_distance);
}

//! Check if the column is degenerate
/*!
 @param tolerance Allows for approximately degenerate columns
 @return True if the column is degenerate, false otherwise
 @note The column is considered degenerate if its extrusion vector is in the
       plane of its base surface, i.e., that the cross product between the
	   normal of the base and the extrusion vector is below tolerance
 @note Type S should have a S::normal() function that return the normal of the
       base surface as a Vector3d<T> type
 */
template <class S, class T>
bool Column<S,T>::degenerate(const T& tolerance) const {
	return ( extrusion_vector_.dot(base_.normal()) <= tolerance );
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
